package com.dongrj.framework.core.utils;

import com.dongrj.framework.core.exception.Assert;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.*;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author dongrongjun
 * @date 2020/08/04
 */
@Slf4j
public final class DateUtils {

    /**
     * 日期格式(年月日时分秒)
     */
    public static final String FORMAT_DEFAULT_TIME = "yyyy-MM-dd HH:mm:ss";
    public static final String FORMAT_DEFAULT_TIME_EMPTY = "1970-01-01 00:00:00";
    /**
     * 日期格式(年月日)
     */
    public static final String FORMAT_DEFAULT_DATE = "yyyy-MM-dd";
    /**
     * 日期格式(年月)
     */
    public static final String FORMAT_DEFAULT_MONTH = "yyyy-MM";

    /**
     * 日期格式(纯数字拼接
     */
    public static final String FORMAT_DEFAULT_NUM_SECOND = "yyyyMMddHHmmss";

    /**
     * 日期格式(纯数字拼接)
     */
    public static final String FORMAT_DEFAULT_NUM_DAY = "yyyyMMdd";

    /**
     * 设置一个空时间
     */
    public static Date empty() {
        return parse(FORMAT_DEFAULT_TIME_EMPTY);
    }

    /**
     * 字符串转日期
     * Format: 自动匹配
     *
     * @param time 时间字符串
     */
    public static Date parse(String time) {
        return parse(time, getFormatRegular(time));
    }


    /**
     * 字符串转日期
     * Format: yyyy-MM-dd HH:mm:ss
     *
     * @param time 时间字符串
     */
    public static Date parseDefault(String time) {
        return parse(time, FORMAT_DEFAULT_TIME);
    }


    /**
     * 字符串转日期
     *
     * @param time 时间字符串
     */
    public static Date parse(String time, String format) {
        return parse(time, format, null);
    }

    /**
     * 字符串转日期
     *
     * @param time 时间字符串
     */
    public static Date parse(String time, Date defaultDate) {
        return parse(time, getFormatRegular(time), () -> defaultDate);
    }


    /**
     * 日期添加年数
     *
     * @param time 时间戳
     */
    public static Date toDate(Long time) {
        if (Objects.isNull(time)) {
            return null;
        }
        return new Date(time);
    }


    /**
     * 字符串转日期
     *
     * @param time        时间字符串
     * @param format      时间字符串格式
     * @param defaultDate 时间字符串为空时，或者解析错误时默认时间
     */
    public static Date parse(String time, String format, Supplier<Date> defaultDate) {
        if (defaultDate == null) {
            if (StringUtils.isEmpty(time) || StringUtils.isEmpty(format)) {
                return null;
            }
            try {
                return DateTime.parse(time, DateTimeFormat.forPattern(format)).toDate();
            } catch (Exception e) {
                log.error("parse.e", e);
                return null;
            }
        } else {
            if (StringUtils.isEmpty(time) || StringUtils.isEmpty(format)) {
                return defaultDate.get();
            }
            try {
                return DateTime.parse(time, DateTimeFormat.forPattern(format)).toDate();
            } catch (Exception e) {
                log.error("parse.e", e);
                return defaultDate.get();
            }
        }
    }


    public static class DateItem {

        private Date beginTime;

        private Date endTime;

        public Date getBeginTime() {
            return beginTime;
        }

        void setBeginTime(Date beginTime) {
            this.beginTime = beginTime;
        }

        public Date getEndTime() {
            return endTime;
        }

        void setEndTime(Date endTime) {
            this.endTime = endTime;
        }
    }

    /**
     * 将一段时间分片(通常用于导出、分页优化)
     *
     * @param beginTime    开始时间
     * @param endTime      结束时间
     * @param intervalTime 间隔时间
     * @return 各个时间段
     */
    public static List<DateItem> shardDate(Date beginTime, Date endTime, int intervalTime) {
        if (ObjectUtils.isAllNull(beginTime, endTime)) {
            return Lists.newArrayList();
        }
        if (intervalTime < 1) {
            return Lists.newArrayList();
        }
        if (endTime.compareTo(beginTime) <= 0) {
            return Lists.newArrayList();
        }
        List<Date> result = Lists.newArrayList(beginTime);
        while (true) {
            beginTime = addMillis(beginTime, intervalTime);
            if (beginTime.compareTo(endTime) >= 0) {
                result.add(endTime);
                break;
            } else {
                result.add(beginTime);
            }
        }
        List<DateItem> dateItemList = Lists.newArrayList();
        for (int i = 0; i < result.size() - 1; i++) {
            DateItem dateItem = new DateItem();
            dateItem.setBeginTime(result.get(i));
            dateItem.setEndTime(result.get((i + 1) >= (result.size()) ? result.size() - 1 : i + 1));
            dateItemList.add(dateItem);
        }
        return dateItemList;
    }

    /**
     * 根据年月日时分秒创建日期
     *
     * @param year    年
     * @param month   月
     * @param day     日
     * @param hour    时
     * @param minute  分
     * @param seconds 秒
     */
    public static Date createDate(int year, int month, int day, int hour, int minute, int seconds) {
        return new DateTime(year, month, day, hour, minute, seconds).toDate();
    }


    /**
     * 获取该日期所在天的最小时间
     *
     * @param date 日期
     */
    public static Date minTimeOfDay(Date date) {
        return new LocalDateTime(date).withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0).toDate();
    }


    /**
     * 获取该日期所在天的最大时间
     *
     * @param date 日期
     */
    public static Date maxTimeOfDay(Date date) {
        return new LocalDateTime(date).withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59).toDate();
    }


    /**
     * 获取该日期所在月的最小日期(返回日期的时分秒为传入日期的时分秒)
     *
     * @param date 日期
     */
    public static Date minDayOfMonthNowTime(Date date) {
        return new DateTime(date).dayOfMonth().withMinimumValue().toDate();
    }

    /**
     * 获取该日期所在月的最小日期(返回日期的时分秒为传入日期的时分秒)
     *
     * @param format 日期
     */
    public static Date minDayOfMonthNowTime(String time, String format) {
        DateTimeFormatter jodaFormat = DateTimeFormat.forPattern(format);
        Date date = DateTime.parse(time, jodaFormat).toDate();
        return minDayOfMonthNowTime(date);
    }


    /**
     * 获取该日期所在月的最小日期(返回日期的时分秒为传入日期的时分秒)
     *
     * @param time 日期
     */
    public static Date minDayOfMonthNowTime(String time) {
        DateTimeFormatter formatter = DateTimeFormat.forPattern(getFormatRegular(time));
        Date date = DateTime.parse(time, formatter).toDate();
        return minDayOfMonthNowTime(date);
    }


    /**
     * 日期添加天数
     *
     * @param date 日期
     * @param days 天数
     */
    public static Date addDays(Date date, int days) {
        return new DateTime(date).plusDays(days).toDate();
    }


    /**
     * 日期添加天数
     *
     * @param date    日期
     * @param minutes 分钟
     */
    public static Date addMinutes(Date date, int minutes) {
        return new DateTime(date).plusMinutes(minutes).toDate();
    }

    /**
     * 日期添加天数
     *
     * @param date    日期
     * @param seconds 秒
     */
    public static Date addSeconds(Date date, int seconds) {
        return new DateTime(date).plusSeconds(seconds).toDate();
    }

    /**
     * 日期添加天数
     *
     * @param date   日期
     * @param millis 毫秒
     */
    public static Date addMillis(Date date, int millis) {
        return new DateTime(date).plusMillis(millis).toDate();
    }


    /**
     * 获取日期之间的天数
     *
     * @param ltDate 较小日期
     * @param gtDate 较大日期
     */
    public static int betweenDays(Date ltDate, Date gtDate) {
        return Days.daysBetween(new LocalDate(ltDate), new LocalDate(gtDate)).getDays();
    }

    /**
     * 获取日期之间的分钟数
     *
     * @param ltDate 较小日期
     * @param gtDate 较大日期
     */
    public static int betweenMinutes(Date ltDate, Date gtDate) {
        return Minutes.minutesBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getMinutes();
    }


    /**
     * 获取日期之间的秒数
     *
     * @param ltDate 较小日期
     * @param gtDate 较大日期
     */
    public static int betweenSeconds(Date ltDate, Date gtDate) {
        return Seconds.secondsBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getSeconds();
    }


    /**
     * 获取日期之间时间差
     *
     * @param ltDate 较小日期
     * @param gtDate 较大日期
     */
    public static long betweenTime(Date ltDate, Date gtDate, TimeUnit timeUnit) {
        if (timeUnit == TimeUnit.MICROSECONDS) {
            int seconds = Seconds.secondsBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getSeconds();
            return seconds * 1000;
        }
        if (timeUnit == TimeUnit.DAYS) {
            return Days.daysBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getDays();
        }
        if (timeUnit == TimeUnit.HOURS) {
            return Hours.hoursBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getHours();
        }
        if (timeUnit == TimeUnit.MINUTES) {
            return Minutes.minutesBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getMinutes();
        }
        if (timeUnit == TimeUnit.SECONDS) {
            return Seconds.secondsBetween(new LocalDateTime(ltDate), new LocalDateTime(gtDate)).getSeconds();
        }
        throw new RuntimeException("不合法的时间格式!");
    }


    /**
     * 获取日期之间的天数
     *
     * @param ltDate 较小日期
     * @param gtDate 较大日期
     */
    public static int betweenDays(String ltDate, String gtDate) {
        return betweenDays(parse(ltDate), parse(gtDate));
    }


    /**
     * 获取一天的小时数
     */
    public static int getHour(Date date) {
        return new LocalDateTime(date).getHourOfDay();
    }


    public static int getDayOfWeek(Date date) {
        return new LocalDateTime(date).getDayOfWeek();
    }

    public static int getWeekOfWeekYear(Date date) {
        return new LocalDateTime(date).getWeekOfWeekyear();
    }

    public static int getWeekYear(Date date) {
        return new LocalDateTime(date).getWeekyear();
    }

    public static int getDayOfMonth(Date date) {
        return new LocalDateTime(date).getDayOfMonth();
    }

    public static int getMonthOfYear(Date date) {
        return new LocalDateTime(date).getMonthOfYear();
    }

    public static int getYear(Date date) {
        return new LocalDateTime(date).getYear();
    }

    /**
     * 设置日期时分秒
     *
     * @param date   日期
     * @param hour   时
     * @param minute 分
     * @param second 秒
     */
    public static Date setTime(Date date, int hour, int minute, int second) {
        return new LocalDateTime(date).withHourOfDay(hour).withMinuteOfHour(minute).withSecondOfMinute(second).toDate();
    }

    public static Date setTimeWithWeek(Date date, int week, int hour, int minute, int second) {
        return new LocalDateTime(date).withWeekOfWeekyear(week).withHourOfDay(hour).withMinuteOfHour(minute).withSecondOfMinute(second).toDate();
    }

    /**
     * 设置日期时分秒
     *
     * @param time   日期
     * @param hour   时
     * @param minute 分
     * @param second 秒
     */
    public static Date setTime(String time, int hour, int minute, int second) {
        return new LocalDateTime(time).withHourOfDay(hour).withMinuteOfHour(minute).withSecondOfMinute(second).toDate();
    }

    /**
     * 获取某月中最小日期以及最小时间时间 yyyy-MM-01 00:00:00
     *
     * @param time 日期
     */
    public static Date minDayOfMonthMinTime(String time) {
        return new LocalDateTime(time).dayOfMonth().withMaximumValue().withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59).toDate();
    }


    /**
     * 获取某月中最大日期以及最小时间时间 yyyy-MM-(29|30|31) now time
     *
     * @param date 日期
     */
    public static Date maxDayOfMonthNowTime(Date date) {
        return new LocalDateTime(date).dayOfMonth().withMaximumValue().toDate();
    }

    /**
     * 获取某月中最大日期以及最小时间时间 yyyy-MM-(29|30|31) 23:59:59
     *
     * @param date 日期
     */
    public static Date maxDayOfMonthMaxTime(Date date) {
        return new LocalDateTime(date).dayOfMonth().withMaximumValue().withHourOfDay(23).withMinuteOfHour(59).withSecondOfMinute(59).toDate();
    }


    /**
     * 获取某月中最大日期以及最小时间时间 yyyy-MM-(29|30|31) 00:00:00
     *
     * @param date 日期
     */

    public static Date maxDayOfMonthMinTime(Date date) {
        return new LocalDateTime(date).dayOfMonth().withMaximumValue().withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0).toDate();
    }

    /**
     * 日期格式化
     *
     * @param date   日期
     * @param format 格式
     */
    public static String toString(Date date, String format) {
        if (date == null) {
            return "";
        }
        return new DateTime(date).toString(format);
    }

    /**
     * 日期格式化
     *
     * @param date 日期
     */
    public static String toString(Date date) {
        return toString(date, getFormatDefault());
    }


    /**
     * 日期格式化yyyyMMddHHmmss
     *
     * @param date 日期
     */
    public static String toNumber(Date date) {
        return toNumber(date, FORMAT_DEFAULT_NUM_SECOND);
    }

    /**
     * 日期格式化成数字
     *
     * @param date   日期
     * @param format 格式化
     */
    public static String toNumber(Date date, String format) {
        return new DateTime(date).toString(format);
    }


    /**
     * 日期加天
     *
     * @param date 日期
     * @param days 天数
     */
    public Date plusDays(Date date, int days) {
        return new DateTime(date).plusDays(days).toDate();
    }


    /**
     * 日期加月
     *
     * @param date   日期
     * @param months 月数
     */
    public Date plusMonths(Date date, int months) {
        return new DateTime(date).plusMonths(months).toDate();
    }

    /**
     * 日期加年
     *
     * @param date  日期
     * @param years 年数
     */
    public Date plusYears(Date date, int years) {
        return new DateTime(date).plusYears(years).toDate();
    }

    /**
     * 正则获取日期的格式化类型
     *
     * @param time 日期
     */
    private static String getFormatRegular(String time) {
        return getFormatRegularWithDefault(time, getFormatDefault());
    }

    /**
     * 正则获取日期的格式化类型
     *
     * @param time 日期
     */
    public static String getFormatRegularWithDefault(String time, String defaultFormat) {
        if (StringUtils.isEmpty(time)) {
            return defaultFormat;
        }
        if (time.matches("\\d{4}-\\d{1,2}-\\d{1,2} \\d{2}:\\d{2}:\\d{2}")) {
            return FORMAT_DEFAULT_TIME;
        }
        if (time.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) {
            return FORMAT_DEFAULT_DATE;
        }
        if (time.matches("\\d{4}-\\d{1,2}")) {
            return FORMAT_DEFAULT_MONTH;
        }
        return defaultFormat;
    }

    /**
     * 当前时间
     */
    public static Date now() {
        return new Date();
    }

    /**
     * 倒计时
     *
     * @param date    开始时间
     * @param seconds 倒计时秒数
     */
    public static long countDownSecondTime(Date date, long seconds) {
        Date now = now();
        if (date.after(now)) {
            return 0L;
        }
        long diffSecondTime = (now.getTime() - date.getTime()) / 1000;
        long result = seconds - diffSecondTime;
        if (result < 0) {
            return 0L;
        }
        return result;
    }


    /**
     * 最近开始时间(T-1)
     *
     * @param date 开始时间
     * @param days 最近几天
     */
    public static Date recentBeginDate(Date date, int days) {
        final Date beginTime = DateUtils.addDays(date, 0 - days);
        return setTime(beginTime, 0, 0, 0);
    }


    /**
     * 最近结束时间(T-1)
     *
     * @param date 开始时间
     * @param days 最近几天
     */
    public static Date recentEndDate(Date date, int days) {
        final Date endTime = DateUtils.addDays(date, -1);
        return setTime(endTime, 23, 59, 59);
    }

    /**
     * 将一段时间按天分片
     *
     * @param beginTime 开始时间
     * @param endTime   结束时间
     * @return 各个时间段
     */
    public static List<Date> shardByDay(Date beginTime, Date endTime) {
        List<Date> dateList = Lists.newArrayList();
        dateList.add(beginTime);
        while (true) {
            beginTime = DateUtils.addDays(beginTime, 1);
            if (beginTime.compareTo(endTime) < 0) {
                if (DateUtils.betweenDays(beginTime, endTime) < 1) {
                    break;
                }
                dateList.add(beginTime);
            } else {
                break;
            }
        }
        dateList.add(endTime);
        return dateList;
    }


    /**
     * 判断两个时间段是否有交集
     *
     * @param start1 开始时间1
     * @param end1   结束时间1
     * @param start2 开始时间2
     * @param end2   结束时间2
     */
    public static boolean overlaps(Date start1, Date end1, Date start2, Date end2) {
        Assert.notEmpty(start1, "时间不能为空!");
        Assert.notEmpty(end1, "时间不能为空!");
        Assert.notEmpty(start2, "时间不能为空!");
        Assert.notEmpty(end2, "时间不能为空!");
        Assert.check(end1.compareTo(start1) > 0, "开始时间不能小于结束时间!");
        Assert.check(end2.compareTo(start2) > 0, "开始时间不能小于结束时间!");
        Interval interval1 = new Interval(new DateTime(start1), new DateTime(end1));
        Interval interval2 = new Interval(new DateTime(start2), new DateTime(end2));
        return interval1.overlaps(interval2);
    }

    /**
     * 根据长度获取日期格式化类型
     *
     * @param time 日期
     */
    private static String getFormatEqualLength(String time) {
        if (StringUtils.isNotEmpty(time)) {
            if (StringUtils.equalLength(time, FORMAT_DEFAULT_TIME)) {
                return FORMAT_DEFAULT_TIME;
            }
            if (StringUtils.equalLength(time, FORMAT_DEFAULT_DATE)) {
                return FORMAT_DEFAULT_DATE;
            }
            if (StringUtils.equalLength(time, FORMAT_DEFAULT_MONTH)) {
                return FORMAT_DEFAULT_MONTH;
            }
        }
        return getFormatDefault();
    }

    private static String getFormatDefault() {
        return FORMAT_DEFAULT_TIME;
    }

}
